package com.tomtop.core.web.config;

import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ThreadFactory;

import org.apache.http.Header;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.message.BasicHeader;
import org.apache.http.nio.conn.NHttpClientConnectionManager;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.AsyncClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.MimeTypeUtils;

@Configuration
@ConditionalOnMissingBean(AsyncClientHttpRequestFactory.class)
@ConditionalOnClass(PoolingNHttpClientConnectionManager.class)
public class HttpAsyncComponentsConfig {

    final static Logger LOGGER = LoggerFactory.getLogger(HttpAsyncComponentsConfig.class);
    
    @ConditionalOnMissingBean( ThreadFactory.class)
	@Bean ThreadFactory threadFactory(){ return new ThreadPoolTaskExecutor(); }
	
	@ConditionalOnMissingBean
	@Bean RequestConfig.Builder requestConfig() { 
		return RequestConfig.custom()
				.setConnectionRequestTimeout(8000)
				.setConnectTimeout(300000)
				.setSocketTimeout(300000)
				.setMaxRedirects(1000)
				.setRedirectsEnabled(true)
				;
	}

	@ConditionalOnMissingBean
	@Bean SocketConfig.Builder socketConfig() { 
		return SocketConfig.custom()
				.setSoTimeout(60000)
				.setSoKeepAlive(true)
				;
	}

	@ConditionalOnMissingBean
	@Bean Collection<? extends Header> defaultHeaders() { return Collections.singletonList(new BasicHeader("Content-Type", MimeTypeUtils.APPLICATION_JSON_VALUE )); }

	@ConditionalOnMissingBean
	@Bean String userAgent(@Value("${spring.application.name:Other}") String app) { return "Laolei HttpClient( "+app+")"; }
	
	@ConditionalOnMissingBean
	@Bean RegistryBuilder<ConnectionSocketFactory> registryConnectionSocketFactory() {
		return RegistryBuilder.<ConnectionSocketFactory>create()
		.register("http", PlainConnectionSocketFactory.getSocketFactory())
		.register("https", SSLConnectionSocketFactory.getSystemSocketFactory())
		; 
	}
	
	@ConditionalOnMissingBean
	@Bean IOReactorConfig.Builder iOReactorConfig(){
		return IOReactorConfig.custom()
				 .setSoKeepAlive(true)
				 .setRcvBufSize(1024*1024*500)
				 .setSndBufSize(1024*1024*200)
				 .setConnectTimeout(5000)
				 .setSoTimeout(8000);
	}

	@Bean ConnectingIOReactor connectingIOReactor(IOReactorConfig.Builder iOReactorConfig,ThreadFactory threadFactory) throws IOReactorException{ 
		return new DefaultConnectingIOReactor(iOReactorConfig.build(), threadFactory);
	}

	@Bean PoolingNHttpClientConnectionManager asyncConnectionPoolManager(ConnectingIOReactor connectingIOReactor)  {
		PoolingNHttpClientConnectionManager poolAyncConnManager=new PoolingNHttpClientConnectionManager(connectingIOReactor);
		poolAyncConnManager.setMaxTotal(1000);
		poolAyncConnManager.setDefaultMaxPerRoute(5);
		return poolAyncConnManager;
	}

	@Bean HttpAsyncClientBuilder asyncClientBuilder(NHttpClientConnectionManager asyncConnectionPoolManager,RequestConfig.Builder requestConfig,ThreadFactory threadFactory,Collection<? extends Header> defaultHeaders,String userAgent){ 
		return HttpAsyncClients.custom()
		  .setConnectionManager(asyncConnectionPoolManager)
		  .setKeepAliveStrategy(new DefaultConnectionKeepAliveStrategy())
		  .setConnectionManagerShared(false)
		  .setThreadFactory(threadFactory)
		  .setRedirectStrategy(new LaxRedirectStrategy())
		  .setDefaultRequestConfig(requestConfig.build())
		  .setDefaultHeaders(defaultHeaders)
		  .setUserAgent(userAgent);
	}

	@Scheduled(fixedDelay=6*60*1000,initialDelay=1000) void showHttpConnectionPoolState(){
		LOGGER.debug("当前http连接池状态 {}",asyncConnectionPoolManager(null).getTotalStats());
	}
	
	@Bean AsyncClientHttpRequestFactory asyncClientHttpRequestFactory(HttpAsyncClientBuilder asyncClientBuilder){
		return new HttpComponentsAsyncClientHttpRequestFactory(asyncClientBuilder.build()); 
	}
}
